#pragma once
#include <vector>
#include <string>
#include <iostream>
#include "TubeTruckTypes.h"
#include "unserialization.h"

/**
 * Notes: const_iterator is fixed at vector<byte>.  This is slightly kludgey
 */

template<typename F, typename... Args> class invoker_base;

#define guts                                                                        \
public:                                                                             \
   invoker_base(F f) : invoker_base<F, Args...>(f) {}                               \
   template<typename ...RArgs>                                                      \
   inline void run(std::vector<byte>::const_iterator& vit,                          \
         const std::vector<byte>::const_iterator &end, const RArgs&... ags) const { \
      A cpy;                                                                        \
      unserialize(vit, end, cpy);                                                   \
      invoker_base<F, Args...>::run(vit, end, ags..., cpy);                         \
   }

template<typename F, typename A, typename... Args>
class invoker_base<F, A, Args...> : public invoker_base<F, Args...> {
guts
};

template<typename F, typename A, typename... Args>
class invoker_base<F, const A&, Args...> : public invoker_base<F, Args...> {
guts
};

#undef guts

template<typename F>
class invoker_base<F> {
public:
   invoker_base(F f) : func(f) {}
   
   template<typename ...RArgs>
   inline void run(std::vector<byte>::const_iterator& vit,                          
         const std::vector<byte>::const_iterator &end, const RArgs&... ags) const { 
      func(ags...);
   }

protected:
   F func;
};


#define guts                                                                           \
public:                                                                                \
   invoker_class_base(C* obj, F f) : invoker_class_base<C, F, Args...>(obj, f) {}      \
   template<typename ...RArgs>                                                         \
   inline void run(std::vector<byte>::const_iterator& vit,                             \
        const std::vector<byte>::const_iterator &end, const RArgs&... ags) const {     \
      A cpy;                                                                           \
      unserialize(vit, end, cpy);                                                      \
      invoker_class_base<C, F, Args...>::run(vit, end, ags..., cpy);                   \
   }

template<typename C, typename F, typename... Args> class invoker_class_base;

template<typename C, typename F, typename A, typename... Args>
class invoker_class_base<C, F, A, Args...> : public invoker_class_base<C, F, Args...> {
   guts
};

template<typename C, typename F, typename A, typename... Args>
class invoker_class_base<C, F, const A&, Args...> : public invoker_class_base<C, F, Args...> {
   guts
};

#undef guts

template<typename C, typename F>
class invoker_class_base<C, F> {
public:
   invoker_class_base(C* obj, F f) : obj(obj), func(f) {}
   
   template<typename ...RArgs>
   inline void run(std::vector<byte>::const_iterator& vit,                             
        const std::vector<byte>::const_iterator &end, const RArgs&... ags) const {     
      (obj->*func)(ags...);
   }

protected:
   F func;
   C *obj;
};

class invokable {
public:
   virtual void operator()(std::vector<byte>::const_iterator& vit,
    const std::vector<byte>::const_iterator &end) const = 0;
};

template<typename F> class invoker;

template<typename... Args>
class invoker<void (*)(Args...)>
   : public invoker_base<void (*)(Args...), Args... >, public invokable {
public:

   invoker(void (*f)(Args...)) : invoker_base<void (*)(Args...), Args... >(f) {}

   void operator ()(std::vector<byte>::const_iterator& vit,
    const std::vector<byte>::const_iterator &end) const {
      invoker_base<void (*)(Args...), Args... >::run(vit, end);
   }
};


template<typename C, typename... Args>
class invoker<void (C::*)(Args...)>
   : public invoker_class_base<C, void (C::*)(Args...), Args... >, public invokable {
public:

   invoker(C* obj, void (C::*f)(Args...)) 
    : invoker_class_base<C, void (C::*)(Args...), Args... >(obj, f) {}

   void operator ()(std::vector<byte>::const_iterator& vit,
    const std::vector<byte>::const_iterator &end) const {
      invoker_class_base<C, void (C::*)(Args...), Args... >::run(vit, end);
   }
};
